package com.atguigu.structures.stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 后缀表达式(逆波兰表达式)
 * 与前缀表达式相似，只是运算符位于操作数之后； (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –。
 *
 * @author xr-8
 * @date 2022-08-16
 * @time 22:36
 */
public class PolandNotation {

    public static void main(String[] args) {
        String expression = "(7+6)*3-3";
        System.out.println(expression+"="+calc(expression));
    }

    public static int calc(String expression) {
        List<String> prefixList = toInfixExpressionList(expression);
        System.out.println("中缀="+prefixList.toString());
        List<String> suffixList = parseSuffixExpressionList(prefixList);
        System.out.println("后缀="+suffixList.toString());
        return calculate(suffixList);
    }

    /**
     * 中缀表达式List转后缀表达式List
     * 例如 ArrayList [1,+,(,(,2,+,3,),*,4,),-,5]  =》 ArrayList [1,2,3,+,4,*,+,5,–]
     * 1.初始化两个栈：运算符栈s1和储存中间结果的栈s2；
     * 2.从左至右扫描中缀表达式；
     * 3.遇到操作数时，将其压s2；
     * 4.遇到运算符时，比较其与s1栈顶运算符的优先级：
     * 4.1 如果s1为空，或栈顶运算符为左括号“(”，则直接将此运算符压入s1；
     * 4.2 否则，若优先级比栈顶运算符的高，也将运算符压入s1；
     * 4.3 否则，将s1栈顶的运算符弹出并压入到s2中，再次转到(4)与s1中新的栈顶运算符相比较；
     * 5.遇到括号时：
     * 5.1  如果是左括号“(”，则直接压入s1；
     * 5.2 如果是右括号“)”，则依次弹出s1栈顶的运算符，并压入s2，直到遇到左括号为止，此时将这一对括号丢弃；
     * 6.重复步骤2至5，直到表达式的最右边；
     * 7.将s1中剩余的运算符依次弹出并压入s2；
     * 8.依次弹出s2中的元素并输出，结果的逆序即为中缀表达式对应的后缀表达式。
     *
     * @param infixList
     * @return
     */
    public static List<String> parseSuffixExpressionList(List<String> infixList) {
        Stack<String> s1 = new Stack<>(); // 符号栈
        List<String> s2 = new ArrayList<>(); // 储存中间结果的Lists2

        for (String item : infixList) {
            if (item.matches("\\d+")) { //如果是一个数，加入s2
                s2.add(item);
            } else if (item.equals("(")) { //如果是左括号“(”，则直接压入s1；
                s1.push(item);
            } else if (item.equals(")")) { //如果是右括号“)”，则依次弹出s1栈顶的运算符，并压入s2，直到遇到左括号为止，此时将这一对括号丢弃;
                while (!s1.peek().equals("(")) {
                    s2.add(s1.pop());
                }
                s1.pop(); //将 ( 弹出 s1栈，消除小括号;
            } else {
                //当前运算符的优先级小于等于s1栈顶运算符, 将s1栈顶的运算符弹出并加入到s2中，再次转到(4.1)与s1中新的栈顶运算符相比较
                while (s1.size() != 0 && !s1.peek().equals("(") && CalculatorUtil.getValue(item) <= CalculatorUtil.getValue(s1.peek())) {
                    s2.add(s1.pop());
                }
                s1.push(item); //还需要将item压入栈
            }
        }
        //将s1中剩余的运算符依次弹出并加入s2
        while (s1.size() != 0) {
            s2.add(s1.pop());
        }
        return s2; //注意因为是存放到List, 因此按顺序输出就是对应的后缀表达式对应的List
    }

    /**
     * 将中缀表达式转化为list
     *
     * @param expression
     * @return
     */
    public static List<String> toInfixExpressionList(String expression) {
        List<String> infixList = new ArrayList<>();
        int i = 0; //这时是一个指针，用于遍历 中缀表达式字符串
        String str; // 对多位数的拼接
        char c; // 每遍历到一个字符，就赋值给c
        do {
            //如果c是一个非数字，我需要加入到ls
            if ((c = expression.charAt(i)) < 48 || (c = expression.charAt(i)) > 57) {
                infixList.add("" + c);
                i++;
            } else { //如果是一个数，需要考虑多位数
                str = ""; //先将str 置成"" '0'[48]->'9'[57]
                while (i < expression.length() && (c = expression.charAt(i)) >= 48 && (c = expression.charAt(i)) <= 57) {
                    str += c;//拼接
                    i++;
                }
                infixList.add(str);
            }
        } while (i < expression.length());
        return infixList;//返回
    }

    /**
     * 运算-对逆波兰表达式运算
     * 后缀表达式的计算思路
     * 1.从左至右扫描表达式；
     * 2.遇到数字时，将数字压入堆栈；
     * 3.遇到运算符时，弹出栈顶的两个数，用运算符对它们做相应的计算（次顶元素 和 栈顶元素），并将结果入栈；
     * 4.重复上述过程直到表达式最右端，最后运算得出的值即为表达式的结果。
     *
     * @param suffixList
     * @return
     */
    public static int calculate(List<String> suffixList) {
        // 创建给栈, 只需要一个栈即可
        Stack<String> stack = new Stack<String>();
        // 遍历 ls
        for (String item : suffixList) {
            // 这里使用正则表达式来取出数
            if (item.matches("\\d+")) { // 匹配的是多位数
                // 入栈
                stack.push(item);
            } else {
                // pop出两个数，并运算， 再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
                    res = num1 + num2;
                } else if (item.equals("-")) {
                    res = num1 - num2;
                } else if (item.equals("*")) {
                    res = num1 * num2;
                } else if (item.equals("/")) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("运算符有误");
                }
                //把res 入栈
                stack.push("" + res);
            }

        }
        //最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }
}
